English | 简体中文
a predictable、zero-cost-use、progressive、high performance's enhanced state management solution,work based on dependency mark、ref collection and state broadcast,power you react!
visit official website https://concentjs.github.io/concent-doc to learn more.
review this gif
🖥Online experience
- simple quick start project:
here you can have a quick look on concent's powerful features!!
- todo app:
written with concent, there is another one written with hook&redux, you can visit it and compare how different they are
todo app with hook&redux (author:Gábor Soós@blacksonic)
- standard enterprise project with concent eco-lib(js):
- standard enterprise project with concent eco-lib(ts):
source code see here:https://github.com/fantasticsoul/concent-guid-ts
✨Fetures
- simple core api,use
run
to load model configuration, use register
to decorate class component, or use useConcent
in function component。 - zero-cost-use,no
Provider
any more, the decorated component can be interactive with store by setState
directly.;hello-concent - friendly model configuration,except state, you can also define reducer、computed、watch and init optionally to cover all your scene。
- flexible data consumption granularity,your can consume multi model data with state key level dependency.。
- progressive,except
setState
, you can also use dispatch
or invoke
to change state, separate your business logic and ui completely.。from class to function - enhance component ability,support ref level computed 、watch、emit&on、setup etc(setup is is inspired by vue3)。
- highly consistent coding experience,no matter class component or function component, they can enjoy the same api calling。multi ways to define component
- high performance rendering mechanism,working based on dependency mark、ref collection and state broadcast,built-in renderKey、lazyDispatch、delayBroadcast feature.。long list exact upate、state batch commit、high frequency input&delay broadcast
- clean dom hierarchy,use reverse inheritance strategy for class component by default, to let your react dom tree keep clean。
- middleware and plugin is supported,allow user customize middleware to intercept data changing behavior to do something else, allow user customize plugin to enhance concent ability.。
- de-centralization model configuration,except for configuring models with
run
, user can also call configure
api to configure you model definition near your component, that means you can publish your component to npm with your component model。 - model clone,allow user clone new model by existed model, to meet the abstract factory need.。
- fully typescript support,writing elegant ts code with concent is easy.。
Use with react router
Details see here react-router-concent,expose history
,you can call it anywhere in your app to enjoy the imperative navigation jump.
react-router-concent online demo
Use with redux-dev-tool
Details see here concent-plugin-redux-devtool,track your state changing history。
Use with plugin-loading
Details see here concent-plugin-loading,control all your reducer function's loading status easily。
concent-plugin-loading online demo
📦Quick start
Make sure you have installed nodejs。
Create an app
In your computer,use create-react-app to create an app
$ npm i -g create-react-app
$ create-react-app cc-app
Install concent
After app created,go to the app's root directory,install concent
with npm command.
$ cd cc-app
$ npm i --save concent
or yarn command
$ yarn add concent
Replace App.js file content with the code below
you can also review the online example's file App1-module-state.js
content, and copy it to file App.js
to see the effect.
import React, { Component } from 'react';
import { register, run, useConcent } from 'concent';
run({
counter:{
state:{count:1}
}
})
@register('counter')
class Counter extends Component{
render(){
const add = ()=>this.setState({count:this.state.count+1});
return (
<div>
{this.state.count}
<button onClick={add}>add</button>
</div>
)
}
}
function FnCounter(){
const ctx = useConcent('counter');
const add = ()=>ctx.setState({count:ctx.state.count+1});
return (
<div>
{ctx.state.count}
<button onClick={add}>add</button>
</div>
)
}
export default function App() {
return (
<div className="App">
<Counter />
<FnCounter />
</div>
);
}
🔨Examples with some advanced features
- run concent,load model configuration
import React, { Component, Fragment } from 'react';
import { register, run } from 'concent';
run({
counter: {
state: {
count: 0,
products: [],
type: '',
},
reducer: {
inc(payload=1, moduleState) {
return { count: moduleState.count + payload };
},
dec(payload=1, moduleState) {
return { count: moduleState.count - payload };
},
async inc2ThenDec3(payload, moduleState, actionCtx){
await actionCtx.dispatch('inc', 2);
await actionCtx.dispatch('dec', 3);
}
},
computed:{
count(newState, oldState){
return newState.count * 2;
}
},
watch:{
count(newState, oldState){
console.log(`count changed from ${oldState.count} to ${newState.count}`);
}
},
init: async ()=>{
const state = await api.fetchState();
return state;
}
}
})
recommend user put every part of model configure to separate files,because they have clear responsibilities.
|____models # business models
| |____index.js
| |____counter
| | |____index.js
| | |____reducer.js # change state methods(optional)
| | |____computed.js # computed methods(optional)
| | |____watch.js # watch methods(optional)
| | |____init.js # async state initialization function(optional)
| | |____state.js # module init state(required)
now reducer functions can call each other with function ref directly(not only string)
export function inc(payload=1, moduleState) {
return { count: moduleState.count + payload };
}
export function dec(payload=1, moduleState) {
return { count: moduleState.count - payload };
}
export async function inc2ThenDec3(payload, moduleState, actionCtx){
await actionCtx.dispatch(inc, 2);
await actionCtx.dispatch(dec, 3);
}
you can also call setState
in reducer function block, it is a promisified api.
export updateLoading(loading){
return { loading }
}
export async function inc2ThenDec3(payload, moduleState, actionCtx){
await actionCtx.dispatch(inc, 2);
await actionCtx.setState({loading: true});
await actionCtx.dispatch(dec, 3);
await actionCtx.setState({loading: false});
return { tip: 'you can return some new value in current reducer fn ot not' };
}
- define setup
setup will only been executed before first render, usually for defining some effects or return methods that user can get them from ctx.settings later, so there is no temporary closure method any more in your render function block,
and setup can pass to class and function both, that means you can switch your component definition way between class and function as you like,reuse business logic elegantly。
const setup = ctx => {
console.log('setup only execute one time before first render period');
ctx.on('someEvent', (p1, p2)=> console.log('receive ', p1, p2));
ctx.effect(() => {
fetchProducts();
}, ["type", "sex", "addr", "keyword"]);
ctx.effect(() => {
return () => {
};
}, []);
ctx.effectProps(() => {
const curTag = ctx.props.tag;
if (curTag !== ctx.prevProps.tag) ctx.setState({ tag: curTag });
}, ["tag"]);
ctx.computed('doubleTen', (newState, oldState)=>{
return newState.count * 10;
}, ['count']);
ctx.computed('count', ({count})=>count*2);
ctx.watch('retKey', ()=>{}, ['count']);
const fetchProducts = () => {
const { type, sex, addr, keyword } = ctx.state;
api.fetchProducts({ type, sex, addr, keyword })
.then(products => ctx.setState({ products }))
.catch(err => alert(err.message));
};
const inc = () => {
ctx.setState({ count: this.state.count + 1 });
}
const dec = () => {
ctx.setState({ count: this.state.count - 1 });
}
const incD = () => {
ctx.dispatch('inc');
}
const decD = () => {
ctx.dispatch('dec');
}
return {
inc,
dec,
incD,
decD,
fetchProducts,
changeType: ctx.sync('type'),
};
};
- register as a concent component base on class、renderProps, hook
@register({module:'counter', setup})
class Counter extends Component {
constructor(props, context){
super(props, context);
this.state = {tag: props.tag};
}
render() {
const { count, products, tag } = this.state;
const {inc, dec, indD, decD, fetchProducts, changeType} = this.ctx.settings;
return 'your ui xml...';
}
}
const PropsCounter = registerDumb({module:'counter', setup})(ctx=>{
const { count, products, tag } = ctx.state;
const {inc, dec, indD, decD, fetchProducts, changeType} = ctx.settings;
return 'your ui xml...';
});
function HookCounter(){
const ctx = useConcent({module:'counter', setup});
const { count, products, tag } = ctx.state;
const {inc, dec, indD, decD, fetchProducts, changeType} = ctx.settings;
return 'your ui xml...';
}
⚖️Some online comparative examples
💻Some online examples
⌨️Some git repo
📰Some articles
Pic introduction
cc state broadcast process
cc component working process